from tkinter import *
from tkinter import ttk
from serial import Serial
import struct
import time, threading
import datetime
import pygame
from tkinter import filedialog
from tkinter import messagebox
import json
import sys
# 用于打开程序后的第一个界面，填写此次基本信息。
class Winstart():
	def __init__(self):
		self.root=Tk()
		self.root.title('产线信息录入：')
		# self.root.protocol('WM_DELETE_WINDOW',self.close)
		self.labelxb=Label(self.root,text='线别：')
		self.labelxz=Label(self.root,text='负责人：')
		self.labeldh=Label(self.root,text='计划单号:')
		self.entryxb=Entry(self.root)
		self.entryxz=Entry(self.root)
		self.entrydh=Entry(self.root)
		self.bt=Button(self.root,text='确定',command=self.event)
		self.labelxb.grid(row=0,column=0)
		self.entryxb.grid(row=0,column=1)
		self.labelxz.grid(row=0,column=2)
		self.entryxz.grid(row=0,column=3)
		self.labeldh.grid(row=0,column=4)
		self.entrydh.grid(row=0,column=5)
		self.bt.grid(row=0,column=6)
		self.root.mainloop()
	def event(self):
		global f
		xianbie=self.entryxb.get()
		xianzhang=self.entryxz.get()
		danhao=self.entrydh.get()
		dic={'xb':xianbie,'xz':xianzhang,'dh':danhao}
		list_info.append(dic)
		# 选择生成的文件存放位置
		filenamesave=filedialog.asksaveasfilename(initialdir='D:/',initialfile=xianbie+'线'+xianzhang+danhao+'计划'+'产线异常状况记录表'+str(datetime.datetime.now())[0:11]+'.csv')
		f=open(filenamesave,'w',encoding='gbk')
		f.write('线别'+','+'负责人'+','+'计划单号'+','+'工位号'+','+'异常现象'+','+'时间'+'\n')#写入表头
		self.root.destroy()
		Play().start()		#开启铃声播放线程
		Timetask().start()	#开启定时同步从机线程
		Win()				#开启主界面



# 通过菜单创建新组复位后产生的复位成功提示消息
class Thread_start_new_group(threading.Thread):
	def run(self):
		messagebox.showinfo('复位成功','已创建新组，'+str(btnlist.start)+'至'+str(btnlist.end)+'号')


# 填写新建组的基本信息，点击确定后创建输入复位范围的窗口
class Win_start_new_group():
	def __init__(self):
		self.root=Tk()
		self.root.title('产线信息录入：')
		# self.root.protocol('WM_DELETE_WINDOW',self.close)
		self.labelxb=Label(self.root,text='线别：')
		self.labelxz=Label(self.root,text='负责人：')
		self.labeldh=Label(self.root,text='计划单号:')
		self.entryxb=Entry(self.root)
		self.entryxz=Entry(self.root)
		self.entrydh=Entry(self.root)
		self.bt=Button(self.root,text='确定',command=self.event)
		self.labelxb.grid(row=0,column=0)
		self.entryxb.grid(row=0,column=1)
		self.labelxz.grid(row=0,column=2)
		self.entryxz.grid(row=0,column=3)
		self.labeldh.grid(row=0,column=4)
		self.entrydh.grid(row=0,column=5)
		self.bt.grid(row=0,column=6)
		self.root.mainloop()
	def event(self):
		
		xianbie=self.entryxb.get()
		xianzhang=self.entryxz.get()
		danhao=self.entrydh.get()

		dic={'xb':xianbie,'xz':xianzhang,'dh':danhao}
		list_info.append(dic)
		print(list_info)
		Win_select_range()
		self.root.quit()
		self.root.destroy()

# 输入新建组的范围窗口，点确定后设标志变量menu_reset_flag为True，且为btnlist绑定start，end属性，复位操作由后台线程检测标志变量后操作
class Win_select_range():

	def __init__(self):
		self.root=Tk()
		self.root.title('选择创建新组范围')
		self.label_start=Label(self.root,text='起始工位',width=8)
		self.label_end=Label(self.root,text='结束工位',width=8)
		self.entry_start=Entry(self.root)
		self.entry_end=Entry(self.root)

		self.button=Button(self.root,text='确定',command=self.event)
		self.label_start.grid(row=0,column=0)
		self.entry_start.grid(row=0,column=1)
		self.label_end.grid(row=0,column=2)
		self.entry_end.grid(row=0,column=3)
		self.button.grid(row=0,column=4)
		self.root.mainloop()
	def event(self):
		global menu_reset_flag

		btnlist.start=int(self.entry_start.get())
		btnlist.end=int(self.entry_end.get())
		menu_reset_flag=True
		self.root.quit()
		self.root.destroy()



# 报警记录窗口，当有报警时实例化
class Winmessage():
	def __init__(self,btn):

		self.addr=btn.id
		self.group=btn.group
		self.root=Tk()	
		self.root.title('报警内容记录：')
		self.root.protocol('WM_DELETE_WINDOW',self.close)	#绑定关闭按钮事件
		self.label=Label(self.root,text='异常原因',width=8)
		self.labelgw=Label(self.root,text='工位号：'+str(self.addr)+';  '+str(datetime.datetime.now())[11:19],width=18)
		self.combox=ttk.Combobox(self.root,width=18)
		self.combox['values']=list_yc
		self.combox.current(0)
		self.combox.bind('<Key-Return>',self.ev)	#绑定键盘ENTER键事件方法
		self.bt=Button(self.root,text='确认',width=4)
		self.bt.bind('<Button-1>',self.ev)
		
		self.labelgw.grid(row=0,column=0)
		self.label.grid(row=0,column=1)
		self.combox.grid(row=0,column=2)
		self.bt.grid(row=0,column=3)

		# 确定按钮事件，写入文件工位号，异常信息，时间
	def ev(self,e):
		global xianbie,xianzhang,danhao
		f.write(list_info[self.group]['xb']+','+list_info[self.group]['xz']+','+list_info[self.group]['dh']+','+str(self.addr)+','+self.combox.get()+','+str(datetime.datetime.now())[0:19]+'\n')
		f.flush()
		print(self.combox.get(),'异常')
		self.root.destroy()

		# 关闭按钮事件处理方法，与ev方法区别在于写入数据的异常现象为空
	def close(self):
		global xianbie,xianzhang,danhao
		f.write(list_info[self.group]['xb']+','+list_info[self.group]['xz']+','+list_info[self.group]['dh']+','+str(self.addr)+','+' '+','+str(datetime.datetime.now())[0:19]+'\n')
		f.flush()

		self.root.destroy()

# 音乐播放线程，每一秒检测一次playflag变量，self.playflag为音乐是否正在播放变量
class Play(threading.Thread):
	def __init__(self):
		threading.Thread.__init__(self)
		self.playflag=True
		pygame.mixer.init()
		pygame.mixer.music.load('test1.mp3')

	# run方法中使用self.playflag变量解决每次检测到全局变量playflag为True是从头开始播放音乐的问题

	def run(self):
		global mainloopflag,playflag
		while mainloopflag:
			if self.playflag:
				if playflag>0:
					pygame.mixer.music.play(-1)
					self.playflag=False
				else:
					pass
			else:
				if playflag>0:
					pass
				else:
					pygame.mixer.music.stop()
					self.playflag=True

			time.sleep(1)
		print('铃声线程退出')


# 为报警记录窗口创建一个线程，使用线程开启一个窗口，并调用窗口的mainloop()函数
class Creat_message(threading.Thread):
	def __init__(self,addr):
		threading.Thread.__init__(self)
		self.addr=addr
	def run(self):
		Winmessage(btnlist.li[self.addr-1]).root.mainloop()


# 工位号按钮
class MyButton(Button):

	# 工位号事件函数，按钮状态为128时，直接创建报警记录窗口，传入self
	# 将全局变量btnflag设为True
	def button_event(self):	
		btnlist.btn_event(self)
	def state_change(self,state):
		self.state=state
		self['bg']=dicbg[self.state]




# 该类集合了对按钮实例操作的函数，主要提供给后台线程调用
class Btnlist():
	li=[]
	liprosess=[]
	def  __init__(self):
		self.rtnor=Rt_normal()
		self.rtres=Rt_reset()
		self.rtswi=Rt_switch()
	# 对按钮列表进行一次循环，对每个按钮调用其收发函数RT()
	def loopRT(self):
		print('进入主循环')
		for i in self.li:
			self.rtnor.rt(i)

	# 对所有按钮进行复位，先循环列表复位按钮状态，然后循环列表调用复位收发函数
	# 注：复位收发函数与后台线程收发函数区别，复位收发函数在等待接受数据时采用延时3ms再
	def reset(self,start=1,end=100,group=0):
		global button_reset_flag

		for btn in self.li[start-1:end]:
			btn.state=143
			btn.exist=True
			btn['bg']='#57f'
			btn.click=False
			btn.group=group
		for j in self.li[start-1:end]:
			self.rtres.rt(j)
		button_reset_flag=False


	def reset_green(self,start=1,end=100,group=0):
		global button_reset_green_flag

		for btn in self.li[start-1:end]:
			btn.state=240
			btn.exist=True
			btn['bg']='#0f0'
			btn.click=False
			btn.group=group
		for j in self.li[start-1:end]:
			self.rtres.rt(j)
		button_reset_green_flag=False

	def switchfun(self):
		global button_switch_flag,switch
		print('调用关闭函数')
		
		if (not switch):
			print('关闭彩灯')
			for i in self.li:
				self.rtswi.rt(i)
		else:
			t.timeout=1
			for i in self.li:
				self.rtnor.rt(i)
			t.timeout=0
		button_switch_flag=False


	# 普通按钮事件，直接判断按钮状态并修改状态，由于后台线程也修改按钮状态，有小概率会发生同时修改同一btn导致错误，修改后将该btn放入待处理列表liprosess
	def btn_event(self,btn):
		global playflag
		if(btn.state==143 or btn.state==240):
			return
		elif(btn.state==128):
			btn.state=240
			btn.click=True
			playflag-=1
			btn['bg']=dicbg[btn.state]
			Creat_message(btn.id).start()	#启动create_message线程，该线程会创建异常记录窗口，
		else:
			pass
		self.liprosess.append(btn)

	def button_event_all(self):
		global button_reset_flag,button_switch_flag,menu_reset_flag

		if button_reset_flag:
			self.reset()
		elif button_switch_flag:
			self.switchfun()
		elif menu_reset_flag:
			self.reset(self.start,self.end,1)
			Thread_start_new_group().start()
			menu_reset_flag=False
		elif button_reset_green_flag:
			self.reset_green()
			


	def add(self,btn):
		self.li.append(btn)


class Rt():
	def rt(self,btn):
		pass

# 该类实现对从机发送关闭指令，会对exist变量判断，只对初次复位成功的按钮发送关闭指令，待解决问题：对初次复位失败后加入的按钮需要修改exist变量/OK
class Rt_switch(Rt):
	def rt(self,btn):
		if btn.exist:
			t.write(struct.pack('@B',btn.id))
			time.sleep(0.01)
			t.write(struct.pack('@B',204))
			time.sleep(0.04)
			r=t.read()
			if (len(r)==0):
				btn.state=255
				btn['bg']='#ffa'
			elif(r[0]==204):
				btn.state=143
				btn.click=True
				btn['bg']='#fff'
			else:
				btn.state=143
				btn.click=True
				btn['bg']='#ffa'
		else:
			pass

# 向从机发送复位指令，并根据返回值判断从机是否工作，是否异常
class Rt_reset(Rt):
	def rt(self,btn):
		t.write(struct.pack('@B',btn.id))
		time.sleep(0.01)
		t.write(struct.pack('@B',btn.state))
		time.sleep(0.04)
		r=t.read()
		if (len(r)==0):
			btn.state=255
			btn['bg']='#ccc'
			btn.exist=False
		elif(r[0]==btn.state):
			btn.state=r[0]
			
			btn['bg']=dicbg[btn.state]
		else:
			btn['bg']='#ffa'



class Creat_messag_call(threading.Thread):
	def __init__(self,addr):
		threading.Thread.__init__(self)
		self.addr=addr
	def run(self):
		messagebox.showinfo('警告',str(self.addr)+'号工位呼叫！')


# 后台线程核心函数，每秒发送一个时间点指令，延时一秒后对接受的数据集中处理，然后在对待处理列表进行处理
# 从机返回数据格式为 地址，状态，校验码。且只有经过外部中断的从机才会返回数据
class Rt_sync(Rt):
	def __init__(self):
		self.rtnor=Rt_normal()
	def rt(self):
		global button_reset_flag , button_switch_flag,switch,playflag,timetask_flag,menu_reset_flag,button_reset_green_flag

		if (button_reset_flag or button_switch_flag or menu_reset_flag or button_reset_green_flag):
			btnlist.button_event_all()
		else:
			pass

		if switch:
			if timetask_flag:
				btnlist.loopRT()
				timetask_flag=False
			t.write(struct.pack('@B',153))
			print('时间点信号已发出')
			time.sleep(1.1)
			r=t.read(3)
			while(len(r)==3):
				print('3Bt数据已取出')
				if r[2]==r[0]^r[1]:
					print('数据验证成功')
					if (btnlist.li[r[0]-1].state==128 and r[1]==240):
						playflag-=1
						Creat_message(r[0]).start()

					elif(btnlist.li[r[0]-1].state!=128 and r[1]==128):
						playflag+=1
						Creat_messag_call(r[0]).start()
					btnlist.li[r[0]-1].state_change(r[1])
					btnlist.li[r[0]-1].exist=True

				else:
					print('数据校验失败')
					t.readall()
					btnlist.loopRT()
					print('数据同步成功')
				r=t.read(3)
			while len(btnlist.liprosess)>0:
				self.rtnor.rt(btnlist.liprosess[0])
				btnlist.liprosess.pop(0)
		else:
			pass


class Rt_normal(Rt):
	def rt(self,btn):
		global playflag
		if (btn.exist):
			t.timeout=1
			t.write(struct.pack('@B',btn.id))
			# print('地址已发送')
			if(btn.click):
				t.write(struct.pack('@B',btn.state))
				print('zhilingyifasong')
				btn.click=False
			else:
				t.write(struct.pack('@B',255))
			r=t.read()
			if (len(r)==0):
				print('数据未接受')
				btn.state=255
				btn['bg']='#ffa'
				# btn.exist=False
			elif(r[0]==143 or r[0]==240 or r[0]==128):
				if (btn.state==128 and r[0]==240):
					playflag-=1
					Creat_message(btn.id).start()
				elif (btn.state!=128 and r[0]==128):
					playflag+=1
				else:
					pass
					
				btn.state=r[0]
				print(btn.id,btn.id,btn.id,btn.id,btn.id,btn.id)
				btn['bg']=dicbg[btn.state]
			else:
				pass
			t.timeout=0
	
				
		else:
			pass



class Timetask(threading.Thread):
	def run(self):
		global timetask_flag
		while mainloopflag:
			time.sleep(60)
			timetask_flag=True
		print('定时任务线程结束')

class MainThread(threading.Thread):
	def __init__(self):
		threading.Thread.__init__(self)
		self.rtsync=Rt_sync()

	def run(self):
		global mainloopflag
		while mainloopflag:
			self.rtsync.rt()
		print('后台线程循环结束')

class Win():	
	def __init__(self):
		self.count=1
		self.root=Tk()
		self.root.title('呼叫器控制工具')
		self.root.protocol('WM_DELETE_WINDOW',self.close)

		menubar=Menu(self.root)
		filemenu = Menu(menubar, tearoff=0)  
		filemenu.add_command(label='新建组', command=self.create_new_group) 
		filemenu.add_command(label='重置为绿', command=self.reset_green)
		filemenu.add_command(label='一键OK', command=self.onekey_ok)  
		filemenu.add_command(label='创建多组')  
		filemenu.add_separator()  
		filemenu.add_command(label='退出', command=self.close)  
		menubar.add_cascade(label='操作', menu=filemenu)  
		helpmenu = Menu(menubar, tearoff=0)  
		helpmenu.add_command(label='关于版本')  
		menubar.add_cascade(label='关于', menu=helpmenu)  
		self.root.config(menu=menubar)  

		for i in range(10):
			for j in range(10):
				b=MyButton(self.root,text=str(self.count),width=4)
				b.id=self.count
				b.click=False
				b.state=255
				b.exist=True
				b.group=0
				b['command']=b.button_event
				#b.bind('<Button-1>',b.gre)
				b.grid(row=i,column=j)
				btnlist.add(b)
				self.count=self.count+1


		self.bres=Button(self.root,text='重置',bg='#57f',width=8)
		self.bres['command']=self.res
		self.bres.grid(row=10,column=6,rowspan=1, columnspan=2)
		find_btn=Button(self.root,text='搜索串口',width=8,command=self.find)
		find_btn.grid(row=10,column=0,rowspan=1, columnspan=2)

		self.open_btn=Button(self.root,text='打开串口',width=8,command=self.open_serial)
		self.open_btn.grid(row=10,column=4,rowspan=1, columnspan=2)

		self.com_number=StringVar()
		self.com_chosen=ttk.Combobox(self.root,textvariable=self.com_number,width=7,state='readonly')
		self.com_chosen['values']=[]
		self.com_chosen.grid(row=10,column=2,rowspan=1, columnspan=2)

		self.switch_btn=Button(self.root,text='关闭',width=8,command=self.switch)
		self.switch_btn.grid(row=10,column=8,rowspan=1,columnspan=2)


		self.root.mainloop()

	def onekey_ok(self):
		for i in btnlist.li:
			i.button_event()
			
	def switch(self):
		global switch,button_switch_flag
		if switch:
			switch=False
			button_switch_flag=True
			self.switch_btn['text']='打开'
			
		else:
			switch=True
			button_switch_flag=True
			self.switch_btn['text']='关闭'



	def res(self):
		global button_reset_flag
		if t.is_open:
			button_reset_flag=True
			self.open_btn['state']=DISABLED
			try:
				mainthread.start()
			except:
				pass
		else:
			messagebox.showinfo('error','串口未打开！')

	def open_serial(self):
		try:
			if (not t.isOpen()):
				print(self.com_number.get())
				t.port=self.com_number.get()
				t.baudrate=9600
				t.timeout=0
				t.open()
				print('串口打开成功',self.com_number.get())
				self.open_btn['text']='关闭串口'

			else:
		
				t.close()
				self.open_btn['text']='打开串口'
				print('串口关闭成功',self.com_number.get())
		except:
			pass

	def find(self):
		dic_com=['com1','com2','com3','com4','com5','com6','com7','com8','com9','com10']
		self.li_com2=[]
		print('duankouhao')
		
		for key in dic_com:
			try:
				t=Serial(key,9600,timeout=1)
				t.close()
				self.li_com2.append(key)
			except:
				pass
				#dic[key]=None

		try:
			self.com_chosen['values']=tuple(self.li_com2)
			self.com_chosen.current(1)
			print(self.li_com2)
		except:
			pass

	def close(self):
		global mainloopflag,switch,f
		try:
			mainloopflag=False
			switch=False

			btnlist.switchfun()

			f.close()
			print('文件已关闭')
			time.sleep(1)
			t.close()
			print('串口已关闭')
			pygame.mixer.music.stop()
		except:
			pass
		finally:
			self.root.destroy()
	def create_new_group(self):
		if len(list_info)==1:
			Win_start_new_group()

	def reset_green(self):
		global button_reset_green_flag

		if t.is_open:

			button_reset_green_flag=True
			self.open_btn['state']=DISABLED
			try:
				mainthread.start()
			except:
				pass
		else:
			messagebox.showinfo('error','串口未打开！')
		
f=None		#保存的文件

menu_reset_flag=False
button_reset_flag=False		#复位按钮标志按键
button_reset_green_flag=False	
button_switch_flag=False	#关闭按钮标志变量
timetask_flag=False			#同步标志变量，每分钟同步一次
switch=True  				#用于关闭按钮事件，表示当前状态为或关
list_yc=[]					#从json文件读取的列表，为异常记录的下拉框内容
list_info=[]
fi=open('conli.json','r',encoding='utf-8')
list_yc=json.load(fi)
fi.close()
playflag=0

mainloopflag=True

btnlist=Btnlist()
dicbg={128:'#f00',240:'#0f0',143:'#57f',255:'#aaa',0:'#aaa'}

mainthread =MainThread()
t=Serial()
Winstart()





